/**
 * 
 */
package info.niwota.fwrite;

import java.util.ArrayList;

import org.json.JSONArray;
import org.json.JSONObject;

import android.gesture.Gesture;
import android.gesture.GestureStroke;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.os.Parcel;
import android.os.Parcelable;

/**
 * @author qiangli
 * 
 */
public class GestureElement extends Element implements Parcelable {
	private static final long serialVersionUID = 1L;
	
	private transient Bitmap bitmap = null;

	private float[][] points;

	// bounds
	private float left;
	private float top;
	private float right;
	private float bottom;

	public GestureElement(Gesture g) {
		this.points = toPoints(g.getStrokes());
		RectF b = g.getBoundingBox();
		this.left = b.left;
		this.top = b.top;
		this.right = b.right;
		this.bottom = b.bottom;
	}

	public GestureElement(float[][] points, float left, float top, float right,
			float bottom) {
		this.points = points;
		this.left = left;
		this.top = top;
		this.right = right;
		this.bottom = bottom;
	}

	private Bitmap getBitmap() {
		if (bitmap == null) {
			if (width > 0 && height > 0 && points != null) {
				bitmap = toBitmap(width, height, inset, color);
			}
		}
		return bitmap;
	}

	public void draw(Canvas canvas, Paint paint, float x, float y,
			float height, float lh) {
		Bitmap bm = this.getBitmap();
		if (bm != null) {
			float oy = top * (lh / height);
			canvas.drawBitmap(bm, x, y + oy, paint);
		}
	}

	private Path toPath() {
		Path path = toPath(points);
		return path;
	}

	private static final int BITMAP_RENDERING_WIDTH = 2;
	private static final boolean BITMAP_RENDERING_ANTIALIAS = true;
	private static final boolean BITMAP_RENDERING_DITHER = true;

	private Bitmap toBitmap(int width, int height, int inset, int color) {
		final Bitmap bitmap = Bitmap.createBitmap(width, height,
				Bitmap.Config.ARGB_8888);
		final Canvas canvas = new Canvas(bitmap);
		final Paint paint = new Paint();
		paint.setAntiAlias(BITMAP_RENDERING_ANTIALIAS);
		paint.setDither(BITMAP_RENDERING_DITHER);
		paint.setColor(color);
		paint.setStyle(Paint.Style.STROKE);
		paint.setStrokeJoin(Paint.Join.ROUND);
		paint.setStrokeCap(Paint.Cap.ROUND);
		paint.setStrokeWidth(BITMAP_RENDERING_WIDTH);

		final Path path = toPath();
		final RectF bounds = new RectF();
		path.computeBounds(bounds, true);

		final float sx = (width - 2 * inset) / bounds.width();
		final float sy = (height - 2 * inset) / bounds.height();
		final float scale = sx > sy ? sy : sx;

		paint.setStrokeWidth(2.0f / scale);

		path.offset(-bounds.left + (width - bounds.width() * scale) / 2.0f,
				-bounds.top + (height - bounds.height() * scale) / 2.0f);

		canvas.translate(inset, inset);
		canvas.scale(scale, scale);

		canvas.drawPath(path, paint);

		return bitmap;
	}

	static final float TOUCH_TOLERANCE = 8;

	private static float[][] toPoints(ArrayList<GestureStroke> strokes) {
		final int count = strokes.size();
		float[][] points = new float[count][];
		for (int i = 0; i < count; i++) {
			GestureStroke s = strokes.get(i);
			points[i] = s.points;
		}
		return points;
	}

	private Path toPath(float[][] points) {
		Path path = new Path();
		for (int i = 0; i < points.length; i++) {
			Path p = makePath(points[i]);
			path.addPath(p);
		}
		return path;
	}

	private static Path makePath(final float[] localPoints) {
		final int count = localPoints.length;
		Path path = null;
		float mX = 0;
		float mY = 0;
		for (int i = 0; i < count; i += 2) {
			float x = localPoints[i];
			float y = localPoints[i + 1];
			if (path == null) {
				path = new Path();
				path.moveTo(x, y);
				mX = x;
				mY = y;
			} else {
				float dx = Math.abs(x - mX);
				float dy = Math.abs(y - mY);
				if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
					path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
					mX = x;
					mY = y;
				}
			}
		}
		return path;
	}

	public int describeContents() {
		return 0;
	}

	public JSONObject toJSON() throws Exception {
		//cast float to int to save space/bandwidth
		
		JSONObject obj = super.toJSON(ELEMENT_GESTURE);
		
		//
		obj.put("l", (int)left);
		obj.put("t", (int)top);
		obj.put("r", (int)right);
		obj.put("b", (int)bottom);
		//
		JSONArray ai = new JSONArray();
		for (int i = 0; i < points.length; i++) {
			float[] pa = points[i];
			JSONArray aj = new JSONArray();
			for (int j = 0; j < pa.length; j++) {
				aj.put((int)pa[j]);
			}
			ai.put(aj);
		}
		obj.put("p", ai);

		return obj;
	}

	public GestureElement(JSONObject obj) throws Exception {
		super(obj);
		//
		JSONArray ai = obj.getJSONArray("p");
		float[][] ps = new float[ai.length()][];
		for (int i = 0; i < ps.length; i++) {
			JSONArray aj = ai.getJSONArray(i);
			float[] pa = new float[aj.length()];
			for (int j = 0; j < pa.length; j++) {
				pa[j] = (float) (aj.getDouble(j));
			}
			ps[i] = pa;
		}
		this.points = ps;
		//
		this.bottom = (float)(obj.getDouble("b"));
		this.right = (float)(obj.getDouble("r"));
		this.top = (float)(obj.getDouble("t"));
		this.left = (float)(obj.getDouble("l"));
	}

	public void writeToParcel(Parcel out, int flags) {
		out.writeInt(color);
		out.writeInt(inset);
		out.writeInt(height);
		out.writeInt(width);
		//
		out.writeFloat(left);
		out.writeFloat(top);
		out.writeFloat(right);
		out.writeFloat(bottom);
		//
		out.writeSerializable(points);
	}

	public GestureElement(Parcel in) {
		this.points = (float[][]) in.readSerializable();
		//
		this.bottom = in.readFloat();
		this.right = in.readFloat();
		this.top = in.readFloat();
		this.left = in.readFloat();
		//
		this.width = in.readInt();
		this.height = in.readInt();
		this.inset = in.readInt();
		this.color = in.readInt();
	}

	public static final Parcelable.Creator<GestureElement> CREATOR = new Parcelable.Creator<GestureElement>() {
		public GestureElement createFromParcel(Parcel in) {
			return new GestureElement(in);
		}

		public GestureElement[] newArray(int size) {
			return new GestureElement[size];
		}
	};
}
